import React, {
  ReactElement,
  useEffect,
  useRef,
  useImperativeHandle,
  forwardRef,
  useMemo,
  useCallback
} from 'react';
import {
  requireNativeComponent,
  findNodeHandle,
  NativeModules
} from 'react-native';
import NetInfo, {
  NetInfoSubscription,
  NetInfoState
} from '@react-native-community/netinfo';

import {
  PlayerEvent,
  PlayerStatus,
  OnError,
  Progress,
  MediaPlayer as MediaPlayerProps,
  VideoTrack
} from './index.d';
import { AVPEventType, AVPStatus, TrackDesc } from './constant';
import { isIOS } from '../../constant';
import { toolTipOpen } from '../../util/global';
import Log from '../../common/log';

const RCTMediaPlayer = requireNativeComponent('RCTMediaPlayer');

const MAX_BAND_WIDTH = 30 * 10000;
const TAG = 'MediaPlayer';

let localNetType = '';

const MediaPlayer = (props: MediaPlayerProps, ref: any): ReactElement => {
  const {
    mute = false,
    autoPlay = false,
    paused,
    src,
    rate,
    style,
    onProgress,
    onSeekComplete,
    onPrepare,
    onLoad,
    onLoadStart,
    onEnd,
    onBufferStart,
    onBufferEnd,
    getVideoTracks,
    headers,
    trackIndex,
    defaultBandwidth = MAX_BAND_WIDTH,
    currentTime,
    onError: onLoadError,
    needReset,
    size = { width: 0, height: 0 }
  } = props;

  const mediaPlayer = useRef<any>(null);

  const start = (): void => {
    if (mediaPlayer && mediaPlayer.current) {
      NativeModules.MediaPlayerManager.start(
        findNodeHandle(mediaPlayer.current)
      );
    }
  };

  const pause = (): void => {
    if (mediaPlayer && mediaPlayer.current) {
      NativeModules.MediaPlayerManager.pause(
        findNodeHandle(mediaPlayer.current)
      );
    }
  };

  const setMute = (m: boolean): void => {
    if (mediaPlayer && mediaPlayer.current) {
      NativeModules.MediaPlayerManager.setMute(
        m,
        findNodeHandle(mediaPlayer.current)
      );
    }
  };

  const setVolume = (v: number): void => {
    if (mediaPlayer && mediaPlayer.current) {
      NativeModules.MediaPlayerManager.setVolume(
        v,
        findNodeHandle(mediaPlayer.current)
      );
    }
  };

  const setZOrderOnTop = (onTop: boolean): void => {
    if (isIOS) return;
    if (mediaPlayer && mediaPlayer.current) {
      NativeModules.MediaPlayerManager.setZOrderOnTop(
        findNodeHandle(mediaPlayer.current),
        onTop
      );
    }
  };

  const setZOrderMediaOverlay = (isMediaOverlay: boolean): void => {
    if (isIOS) return;
    if (mediaPlayer && mediaPlayer.current) {
      NativeModules.MediaPlayerManager.setZOrderMediaOverlay(
        findNodeHandle(mediaPlayer.current),
        isMediaOverlay
      );
    }
  };

  const setZOrderOnTopAndMediaOverlay = (): void => {
    if (isIOS) return;
    if (mediaPlayer && mediaPlayer.current) {
      NativeModules.MediaPlayerManager.setZOrderOnTopAndMediaOverlay(
        findNodeHandle(mediaPlayer.current)
      );
    }
  };

  const reset = (): void => {
    if (!isIOS) return;
    if (mediaPlayer && mediaPlayer.current) {
      NativeModules.MediaPlayerManager.reset(
        findNodeHandle(mediaPlayer.current)
      );
    }
  };

  const reload = (): void => {
    if (!isIOS) return;
    if (mediaPlayer && mediaPlayer.current) {
      NativeModules.MediaPlayerManager.reload(
        findNodeHandle(mediaPlayer.current)
      );
    }
  };

  const destroy = (): void => {
    if (mediaPlayer && mediaPlayer.current) {
      NativeModules.MediaPlayerManager.destroy(
        findNodeHandle(mediaPlayer.current)
      );
    }
  };

  const seekToTime = (time: number, accurate?: boolean): void => {
    if (mediaPlayer && mediaPlayer.current) {
      NativeModules.MediaPlayerManager.seekToTime(
        time,
        true,
        findNodeHandle(mediaPlayer.current)
      );
    }
  };

  const selectTrack = (
    selectedTrackIndex: number,
    accurate?: boolean
  ): void => {
    if (mediaPlayer && mediaPlayer.current) {
      NativeModules.MediaPlayerManager.selectTrack(
        selectedTrackIndex,
        !!accurate,
        findNodeHandle(mediaPlayer.current)
      );
    }
  };

  const onPlayerEvent = useCallback(
    ({ nativeEvent }: { nativeEvent: PlayerEvent }): void => {
      const { eventType, duration } = nativeEvent;
      Log.i(
        TAG,
        JSON.stringify({
          event: 'onPlayerEvent',
          eventType,
          duration,
          src
        })
      );

      if (eventType === AVPEventType.AVPEventLoadingStart) {
        if (onBufferStart) {
          onBufferStart();
        }
        return;
      }

      if (eventType === AVPEventType.AVPEventLoadingEnd) {
        if (onBufferEnd) {
          onBufferEnd();
        }
      }

      if (eventType === AVPEventType.AVPEventSeekEnd && isIOS) {
        if (onSeekComplete) {
          onSeekComplete();
        }
      }
    },
    [onBufferEnd, onBufferStart, onSeekComplete, src]
  );

  const onPlayerStatusChanged = useCallback(
    ({ nativeEvent }: { nativeEvent: PlayerStatus }): void => {
      const { status, trackInfo, duration, currentTrack } = nativeEvent;

      Log.i(
        TAG,
        JSON.stringify({
          event: 'onPlayerStatusChanged',
          status,
          duration,
          src
        })
      );

      if (status === AVPStatus.AVPStatusIdle && isIOS) {
        if (onLoadStart) {
          onLoadStart();
        }
        return;
      }

      if (status === AVPStatus.AVPStatusInitialzed && !isIOS) {
        if (onLoadStart) {
          onLoadStart();
        }
        return;
      }

      if (status === AVPStatus.AVPStatusStarted) {
        if (onLoad) {
          onLoad(nativeEvent);
        }
        return;
      }

      if (status === AVPStatus.AVPStatusCompletion) {
        if (onEnd) {
          onEnd();
        }
        return;
      }

      if (status === AVPStatus.AVPStatusPrepared) {
        if (onPrepare) {
          onPrepare(nativeEvent);
        }
        if (getVideoTracks && trackInfo) {
          getVideoTracks(
            trackInfo
              .map(
                (item): VideoTrack => ({
                  trackIndex: item.trackIndex,
                  trackBitrate: Number(item.trackBitrate),
                  trackDesc: TrackDesc[item.videoHeight] || '',
                  resolution: item.videoHeight,
                  resolutionDesc: `${item.videoHeight}P`
                })
              )
              .sort(
                (left, right): number => right.trackBitrate - left.trackBitrate
              ),
            {
              trackIndex: currentTrack.trackIndex,
              trackBitrate: Number(currentTrack.trackBitrate),
              trackDesc: TrackDesc[currentTrack.videoHeight] || '',
              resolution: currentTrack.videoHeight,
              resolutionDesc: `${currentTrack.videoHeight}P`
            }
          );
        }
      }
    },
    [getVideoTracks, onEnd, onLoad, onLoadStart, onPrepare, src]
  );

  const onError = useCallback(
    ({ nativeEvent }: { nativeEvent: OnError }): void => {
      const { code, message } = nativeEvent;
      if (onLoadError) {
        onLoadError({ nativeEvent });
      }
      console.log('on error', code, message);
    },
    [onLoadError]
  );

  const onNativeProgress = useCallback(
    ({ nativeEvent }: { nativeEvent: Progress }): void => {
      if (onProgress) {
        onProgress(nativeEvent);
      }
    },
    [onProgress]
  );

  const onNativeSeekComplete = useCallback((): void => {
    if (onSeekComplete) {
      onSeekComplete();
    }
  }, [onSeekComplete]);

  useEffect((): void => {
    if (typeof trackIndex === 'number' && trackIndex >= 0) {
      selectTrack(trackIndex);
    }
  }, [trackIndex]);

  useImperativeHandle(ref, (): { [key: string]: any } => {
    return {
      seek: (time: number): void => {
        seekToTime(time, true);
      },
      start,
      pause,
      setMute: (m: boolean): void => {
        setMute(m);
      },
      setZOrderOnTop: (onTop: boolean): void => {
        setZOrderOnTop(onTop);
      },
      setZOrderMediaOverlay: (isMediaOverlay: boolean): void => {
        setZOrderMediaOverlay(isMediaOverlay);
      },
      setZOrderOnTopAndMediaOverlay,
      setVolume
    };
  });

  useEffect((): (() => void) => {
    if (currentTime && !isIOS) {
      seekToTime(currentTime, true);
    }
    const handler: NetInfoSubscription | undefined = NetInfo.addEventListener(
      (res: NetInfoState): void => {
        let netType = 'unknown';
        if (typeof res === 'string') {
          netType = res;
        } else if (res.type !== 'cellular') {
          netType = res.type;
        } else {
          netType = res.details.cellularGeneration || 'unknown';
        }

        if (netType === '4g' && localNetType !== '4g') {
          toolTipOpen({ desc: '当前为4G网络播放' });
          if (localNetType !== '') {
            if (needReset) {
              reset();
            }
          }
        } else if (
          netType === 'wifi' &&
          localNetType !== 'wifi' &&
          localNetType !== ''
        ) {
          if (needReset) {
            reset();
          }
        }

        localNetType = netType;
      }
    );
    return (): void => {
      localNetType = '';
      destroy();
      if (handler) {
        handler();
      }
    };
  }, []);

  return useMemo(
    (): ReactElement => (
      <RCTMediaPlayer
        ref={mediaPlayer}
        style={style}
        src={src}
        mute={mute}
        rate={rate}
        autoPlay={autoPlay}
        headers={headers}
        onPlayerEvent={onPlayerEvent}
        onPlayerStatusChanged={onPlayerStatusChanged}
        onError={onError}
        onProgress={onNativeProgress}
        onSeekComplete={onNativeSeekComplete}
        paused={paused}
        defaultBandwidth={defaultBandwidth}
        size={size}
      />
    ),
    [
      style,
      src,
      mute,
      rate,
      autoPlay,
      headers,
      onPlayerEvent,
      onPlayerStatusChanged,
      onError,
      onNativeProgress,
      onNativeSeekComplete,
      paused,
      defaultBandwidth,
      size
    ]
  );
};

export default forwardRef(MediaPlayer);
